/**
 * Copyright (c) 2009 Mujtaba Hassanpur.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

import java.util.*;
import java.awt.*;

/**
 * Represents a puzzle piece, comprised of multiple bricks.
 */
public class PuzzlePiece extends Brick implements BrickCollection, Drawable
{
	public static enum PieceState {NORMAL_FALL, ACCELERATED_FALL};
	public static enum PieceType {SQUARE, T_SHAPE, L_SHAPE, MIRRORED_L, LONG_SHAPE, Z_SHAPE, MIRRORED_Z};
	private static final int[][][] SquareDescriptor = {
		{{0,0},{0,1},{1,0},{1,1}},
		{{0,0},{0,1},{1,0},{1,1}},
		{{0,0},{0,1},{1,0},{1,1}},
		{{0,0},{0,1},{1,0},{1,1}}
	};
	private static final int[][][] TShapeDescriptor = {
		{{0,0},{1,0},{2,0},{1,1}},
		{{0,0},{0,1},{0,2},{1,1}},
		{{0,1},{1,1},{2,1},{1,0}},
		{{0,1},{1,0},{1,1},{1,2}}
	};
	private static final int[][][] LShapeDescriptor = {
		{{0,0},{1,0},{0,1},{0,2}},
		{{0,0},{0,1},{1,1},{2,1}},
		{{0,2},{1,2},{1,1},{1,0}},
		{{0,0},{1,0},{2,0},{2,1}}
	};
	private static final int[][][] MirroredLDescriptor = {
		{{0,0},{1,0},{1,1},{1,2}},
		{{0,0},{0,1},{1,0},{2,0}},
		{{0,0},{0,1},{0,2},{1,2}},
		{{0,1},{1,1},{2,1},{2,0}}
	};
	private static final int[][][] LongShapeDescriptor = {
		{{0,0},{1,0},{2,0},{3,0}},
		{{0,0},{0,1},{0,2},{0,3}},
		{{0,0},{1,0},{2,0},{3,0}},
		{{0,0},{0,1},{0,2},{0,3}}
	};
	private static final int[][][] ZShapeDescriptor = {
		{{0,1},{1,0},{1,1},{2,0}},
		{{0,0},{0,1},{1,1},{1,2}},
		{{0,1},{1,0},{1,1},{2,0}},
		{{0,0},{0,1},{1,1},{1,2}}
	};
	private static final int[][][] MirroredZDescriptor = {
		{{0,0},{1,0},{1,1},{2,1}},
		{{0,2},{0,1},{1,1},{1,0}},
		{{0,0},{1,0},{1,1},{2,1}},
		{{0,2},{0,1},{1,1},{1,0}}
	};
	
	private Vector<Brick> bricks;
	private PieceState pieceState;
	private PieceType pieceType;
	private int orientation; // a value between [0,3].
	
	public PuzzlePiece()
	{
		this(0, 0, Color.white, PieceType.SQUARE);
	}
	
	public PuzzlePiece(Color inColor)
	{
		this(0, 0, inColor, PieceType.SQUARE);
	}
	
	public PuzzlePiece(Color inColor, PieceType type)
	{
		this(0, 0, inColor, type);
	}
	
	public PuzzlePiece(PieceType type)
	{
		this(0, 0, Color.white, type);
	}
	
	public PuzzlePiece(int xpos, int ypos, Color inColor, PieceType type)
	{
		if(xpos >= 0)
			positionX = xpos;
		else
			positionX = 0;
		
		if(ypos >= 0)
			positionY = ypos;
		else
			positionY = 0;
		
		if(inColor != null)
			color = inColor;
		else
			color = Color.white;
		
		pieceState = PieceState.NORMAL_FALL;
		pieceType = type;
		orientation = 0;
		bricks = new Vector<Brick>();
		
		// add the bricks, based on the shape
		if(pieceType == PieceType.SQUARE)
		{
			addBricksFromDescriptor(SquareDescriptor);
		}
		else if(pieceType == PieceType.T_SHAPE)
		{
			addBricksFromDescriptor(TShapeDescriptor);
		}
		else if(pieceType == PieceType.L_SHAPE)
		{
			addBricksFromDescriptor(LShapeDescriptor);
		}
		else if(pieceType == PieceType.MIRRORED_L)
		{
			addBricksFromDescriptor(MirroredLDescriptor);
		}
		else if(pieceType == PieceType.LONG_SHAPE)
		{
			addBricksFromDescriptor(LongShapeDescriptor);
		}
		else if(pieceType == PieceType.Z_SHAPE)
		{
			addBricksFromDescriptor(ZShapeDescriptor);
		}
		else if(pieceType == PieceType.MIRRORED_Z)
		{
			addBricksFromDescriptor(MirroredZDescriptor);
		}
		else
		{
			System.out.println("Couldn't determine PuzzlePiece type.");
		}
	}
	
	public static PuzzlePiece createRandomPuzzlePiece()
	{
		Randomizer rand = Randomizer.getGenerator();
		int colorRed = rand.nextInt(255);
		int colorGreen = rand.nextInt(255);
		int colorBlue = rand.nextInt(255);
		int shapeIndex = rand.nextInt(PieceType.values().length);
		PieceType shape = (PieceType.values())[shapeIndex];
		
		return new PuzzlePiece(new Color(colorRed, colorGreen, colorBlue), shape);
	}
	
	/**
	 * Adds bricks to the puzzle piece based on the given descriptor.
	 * @param desc Reference to a 3-dimensional array of size [4][4][2].
	 */
	private void addBricksFromDescriptor(int[][][] desc)
	{
		for(int i = 0; i < desc[orientation].length; i++)
		{
			bricks.add(new Brick(positionX + desc[orientation][i][0], positionY + desc[orientation][i][1], color));
		}
	}
	
	/**
	 * Returns the current descriptor based on the shape of the puzzle piece.
	 * @return A 3-dimensional array of size [4][4][2].
	 */
	private int[][][] getCurrentDescriptor()
	{
		int[][][] result = null;
		switch(pieceType)
		{
			case SQUARE:
				result = SquareDescriptor;
				break;
			case T_SHAPE:
				result = TShapeDescriptor;
				break;
			case LONG_SHAPE:
				result = LongShapeDescriptor;
				break;
			case L_SHAPE:
				result = LShapeDescriptor;
				break;
			case MIRRORED_L:
				result = MirroredLDescriptor;
				break;
			case Z_SHAPE:
				result = ZShapeDescriptor;
				break;
			case MIRRORED_Z:
				result = MirroredZDescriptor;
				break;
		}
		
		return result;
	}
	
	/**
	 * Returns a descriptor based on the shape and orientation of the puzzle
	 * piece.
	 * 
	 * @return A 2-dimensional array of size [4][2].
	 */
	private int[][] getCurrentOrientationDescriptor()
	{
		int[][] result = null;
		int[][][] desc = getCurrentDescriptor();
		if(desc != null)
		{
			result = desc[orientation];
		}
		
		return result;
	}
	
	/**
	 * Updates the bricks based on the current orientation and shape. Returns
	 * true if successful, otherwise false.
	 */
	private boolean updateBricks()
	{
		boolean result = false;
		int[][] desc = getCurrentOrientationDescriptor();
		if(desc != null)
		{
			Iterator iterate = bricks.iterator();
			Brick temp;
			for(int i = 0; i < desc.length; i++)
			{
				if(iterate.hasNext())
				{
					temp = (Brick)iterate.next();
					temp.setPositionX(positionX + desc[i][0]);
					temp.setPositionY(positionY + desc[i][1]);
				}
			}
			
			result = true;
		}
		
		return result;
	}
	
	public boolean setPositionX(int xpos)
	{
		boolean result = false;
		if(xpos >= 0)
		{
			positionX = xpos;
			updateBricks();
			result = true;
		}
		
		return result;
	}
	
	public boolean setPositionY(int ypos)
	{
		boolean result = false;
		if(ypos >= 0)
		{
			positionY = ypos;
			updateBricks();
			result = true;
		}
		
		return result;
	}
	
	/**
	 * Get the top boundary of the piece based on the current shape and
	 * orientation.
	 * 
	 * @return Returns the top boundary; a value between 1 and 4.
	 */
	public int getBoundaryTop()
	{
		int[][] desc = getCurrentOrientationDescriptor();
		int result = 0;
		for(int i = 0; i < desc.length; i++)
		{
			if((desc[i][1] + 1) > result)
				result = desc[i][1] + 1;
		}
		
		return result;
	}
	
	/**
	 * Get the right boundary of the piece based on the current shape and
	 * orientation.
	 * 
	 * @return Returns the right boundary; a value between 1 and 4.
	 */
	public int getBoundaryRight()
	{
		int[][] desc = getCurrentOrientationDescriptor();
		int result = 0;
		for(int i = 0; i < desc.length; i++)
		{
			if((desc[i][0] + 1) > result)
				result = desc[i][0] + 1;
		}
		
		return result;
	}
	
	/**
	 * Changes the orientation of the puzzle piece.
	 */
	public void cycleOrientation()
	{
		orientation = (orientation + 1) %4;
		updateBricks();
	}
	
	/**
	 * Moves the puzzle piece to the left. Also handles collision detection.
	 */
	public void moveLeft()
	{
		int nextPos = positionX - 1;
		
		// enforce left boundary
		if(nextPos >= 0)
		{
			Brick[][] matrix = Bucket.getBucket().getCollisionMatrix();
			Iterator i = bricks.iterator();
			while(i.hasNext())
			{
				Brick temp = (Brick)i.next();
				int tempNextX = temp.getPositionX()-1;
				int tempNextY = temp.getPositionY();
				if(tempNextX < Bucket.BUCKET_WIDTH && tempNextY < Bucket.BUCKET_HEIGHT && matrix[tempNextY][tempNextX] != null)
				{
					// we found a collision, so dont move
					return;
				}
			}
			
			// if we made it here, then there is no collision
			setPositionX(nextPos);
		}
	}
	
	/**
	 * Moves the piece to the right. Also handles collision detection.
	 */
	public void moveRight()
	{
		int rightBound = Bucket.BUCKET_WIDTH - (getBoundaryRight());
		int nextPos = positionX + 1;
		
		// enforce right boundary
		if(nextPos <= rightBound)
		{
			Brick[][] matrix = Bucket.getBucket().getCollisionMatrix();
			Iterator i = bricks.iterator();
			while(i.hasNext())
			{
				Brick temp = (Brick)i.next();
				int tempNextX = temp.getPositionX()+1;
				int tempNextY = temp.getPositionY();
				if(tempNextX < Bucket.BUCKET_WIDTH && tempNextY < Bucket.BUCKET_HEIGHT && matrix[tempNextY][tempNextX] != null)
				{
					// we found a collision, so don't move
					return;
				}
			}
			
			// if we make it here, then we know there are no collisions
			setPositionX(nextPos);
		}
	}
	
	/**
	 * Moves the piece down. Also handles collision detection.
	 */
	public void moveDown()
	{
		int nextPos = positionY - 1;
		boolean detectCollision = false;
		
		// enforce bottom boundary
		if(nextPos >= 0)
		{
			Brick[][] matrix = Bucket.getBucket().getCollisionMatrix();
			Iterator i = bricks.iterator();
			while(!detectCollision && i.hasNext())
			{
				Brick temp = (Brick)i.next();
				int tempNextX = temp.getPositionX();
				int tempNextY = temp.getPositionY()-1;
				if(tempNextX < Bucket.BUCKET_WIDTH && tempNextY < Bucket.BUCKET_HEIGHT && matrix[tempNextY][tempNextX] != null)
				{
					// we found a collision, in this case notify the bucket
					detectCollision = true;
				}
			}
			
			if(!detectCollision)
			{
				// if we make it here, then we know there are no collisions
				setPositionY(nextPos);
			}
		}
		else
		{
			// we'd hit the bottom boundary
			detectCollision = true;
		}
		
		if(detectCollision)
		{
			Bucket.getBucket().dumpActivePuzzlePieceToBucket();
		}
	}
	
	/**
	 * Gets the current state of the piece.
	 * @return Returns the state.
	 */
	public PieceState getState()
	{
		return pieceState;
	}
	
	/**
	 * Sets the state of the piece.
	 * @param newState New state.
	 */
	public void setState(PieceState newState)
	{
		pieceState = newState;
	}
	
	public void add(Brick e)
	{
		throw new UnsupportedOperationException();
	}
	
	public void remove(Brick e)
	{
		throw new UnsupportedOperationException();
	}
	
	public Iterator getIterator()
	{
		return bricks.iterator();
	}
	
	public void draw(Graphics2D g)
	{
		Iterator i = bricks.iterator();
		while(i.hasNext())
		{
			((Brick)(i.next())).draw(g);
		}
	}
}
